﻿/* Copyright (c) 2010, Andreas Grünwald
 * All rights reserved.
 *
 * Some parts of the StringExtensions class in this file is based on code from Philipp Sumi
 * See http://www.hardcodet.net/2009/04/dpapi-string-encryption-and-extension-methods
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;

namespace yaTDL.Extensions
{
    /// <summary>
    /// Provides extension methods for <see cref="String"/> and <see cref="String[]"/>
    /// </summary>
    /// <remarks>
    /// Some parts of this class based on code from Philipp Sumi. See http://www.hardcodet.net/2009/04/dpapi-string-encryption-and-extension-methods fro more information.</remarks>
    public static class StringExtensions
    {
        /// <summary>
        /// Specifies the data protection scope of the DPAPI.
        /// </summary>
        /// <remarks>Based on code from Philipp Sumi. See http://www.hardcodet.net/2009/04/dpapi-string-encryption-and-extension-methods for more information.</remarks>
        private const DataProtectionScope Scope = DataProtectionScope.CurrentUser;

        /// <summary>
        /// Encrypts a given password and returns the encrypted data
        /// as a base64 string.
        /// </summary>
        /// <param name="plainText">An unencrypted string that needs
        /// to be secured.</param>
        /// <returns>A base64 encoded string that represents the encrypted
        /// binary data.
        /// </returns>
        /// <remarks>This solution is not really secure as we are
        /// keeping strings in memory. If runtime protection is essential,
        /// <see cref="SecureString"/> should be used.</remarks>
        /// <remarks>Based on code from Philipp Sumi. See http://www.hardcodet.net/2009/0
        public static string Encrypt(this string plainText)
        {
            if (String.IsNullOrEmpty(plainText)) { return ""; }

            if (plainText == null) throw new ArgumentNullException("plainText");

            //encrypt data
            var data = Encoding.Unicode.GetBytes(plainText);
            byte[] encrypted = ProtectedData.Protect(data, null, Scope);

            //return as base64 string
            return Convert.ToBase64String(encrypted);
        }

        /// <summary>
        /// Decrypts a given string.
        /// </summary>
        /// <param name="cipher">A base64 encoded string that was created
        /// through the <see cref="Encrypt(string)"/> or
        /// <see cref="Encrypt(SecureString)"/> extension methods.</param>
        /// <returns>The decrypted string.</returns>
        /// <remarks>Keep in mind that the decrypted string remains in memory
        /// and makes your application vulnerable per se. If runtime protection
        /// is essential, <see cref="SecureString"/> should be used.</remarks>
        /// <remarks>Based on code from Philipp Sumi. See http://www.hardcodet.net/2009/0
        public static string Decrypt(this string cipher)
        {
            if (String.IsNullOrEmpty(cipher)) { return ""; }

            if (cipher == null) throw new ArgumentNullException("cipher");

            //parse base64 string
            byte[] data = Convert.FromBase64String(cipher);

            //decrypt data
            byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);
            return Encoding.Unicode.GetString(decrypted);
        }       

        /// <summary>
        /// Decrypts a base64 encrypted string and returns the decrpyted data
        /// wrapped into a <see cref="SecureString"/> instance.
        /// </summary>
        /// <param name="cipher">A base64 encoded string that was created
        /// through the <see cref="Encrypt(string)"/> or
        /// <see cref="Encrypt(SecureString)"/> extension methods.</param>
        /// <returns>The decrypted string, wrapped into a
        /// <see cref="SecureString"/> instance.</returns>
        /// <remarks>Based on code from Philipp Sumi. See http://www.hardcodet.net/2009/0
        public static SecureString DecryptSecure(this string cipher)
        {
            if (String.IsNullOrEmpty(cipher)) return "".ToSecureString();

            //parse base64 string
            byte[] data = Convert.FromBase64String(cipher);

            //decrypt data
            byte[] decrypted = ProtectedData.Unprotect(data, null, Scope);

            SecureString ss = new SecureString();

            //parse characters one by one - doesn't change the fact that
            //we have them in memory however...
            int count = Encoding.Unicode.GetCharCount(decrypted);
            int bc = decrypted.Length / count;
            for (int i = 0; i < count; i++)
            {
                ss.AppendChar(Encoding.Unicode.GetChars(decrypted, i * bc, bc)[0]);
            }

            //mark as read-only
            ss.MakeReadOnly();
            return ss;
        }

        /// <summary>
        /// Wraps a managed string into a <see cref="SecureString"/> 
        /// instance.
        /// </summary>
        /// <param name="value">A string or char sequence that 
        /// should be encapsulated.</param>
        /// <returns>A <see cref="SecureString"/> that encapsulates the
        /// submitted value.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="value"/>
        /// is a null reference.</exception>
        /// <remarks>Based on code from Philipp Sumi. See http://www.hardcodet.net/2009/0
        public static SecureString ToSecureString(this IEnumerable<char> value)
        {
            var secured = new SecureString();

            var charArray = value.ToArray();
            for (int i = 0; i < charArray.Length; i++)
            {
                secured.AppendChar(charArray[i]);
            }

            secured.MakeReadOnly();
            return secured;
        }

        /// <summary>
        /// Generates the MD5 hash of the string
        /// </summary>
        /// <param name="value">The String to be hashed.</param>
        /// <returns>Returns the MD5 has of the string.</returns>
        public static string GetMD5Hash(this string value)
        {
            System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
            byte[] data = System.Text.Encoding.ASCII.GetBytes(value);
            data = x.ComputeHash(data);
            string ret = "";
            for (int i = 0; i < data.Length; i++)
                ret += data[i].ToString("x2").ToLower();
            return ret;
        }

        /// <summary>
        /// Cobines all values of a string array into a single string of the format "value[0]; values[1];....; values[n];"
        /// If values only contains one item, the first item will be returned without being formatted in any way,
        /// </summary>
        /// <param name="values">The string array to be combined.</param>
        /// <returns>Returns the combines string.</returns>
        public static string ToCombinedString(this string[] values)
        {
            int length = values.Length;

            if (length == 0) return "";

            if (length == 1) return values.First();

            string result = "";

            foreach (string str in values)
            {
                result += String.Format("{0}; ", str);
            }

            return result.TrimEnd();
        }
    }
}